<!DOCTYPE html>
<html lang="en-US">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <title>深入 JS 之作用域和闭包 | Top 的博客 ｜ Top Blog</title>
    <meta name="generator" content="VuePress 1.9.9">
    <link rel="icon" href="/blog/favicon.ico">
    <meta name="description" content="个人博客">
    <meta name="viewport" content="width=device-width,initial-scale=1,user-scalable=no">
    
    <link rel="preload" href="/blog/assets/css/0.styles.247a7745.css" as="style"><link rel="preload" href="/blog/assets/js/app.a26f0e63.js" as="script"><link rel="preload" href="/blog/assets/js/6.dddf2d67.js" as="script"><link rel="preload" href="/blog/assets/js/9.876e8c50.js" as="script"><link rel="prefetch" href="/blog/assets/js/10.ad9b1204.js"><link rel="prefetch" href="/blog/assets/js/11.9d7155e6.js"><link rel="prefetch" href="/blog/assets/js/12.878de5aa.js"><link rel="prefetch" href="/blog/assets/js/13.d4356719.js"><link rel="prefetch" href="/blog/assets/js/14.228aba71.js"><link rel="prefetch" href="/blog/assets/js/15.d31bc4e7.js"><link rel="prefetch" href="/blog/assets/js/16.0d4d14f2.js"><link rel="prefetch" href="/blog/assets/js/17.669b199a.js"><link rel="prefetch" href="/blog/assets/js/2.8a74ff50.js"><link rel="prefetch" href="/blog/assets/js/3.e32a4c38.js"><link rel="prefetch" href="/blog/assets/js/4.b134dc4c.js"><link rel="prefetch" href="/blog/assets/js/5.511daf51.js"><link rel="prefetch" href="/blog/assets/js/7.1919884f.js"><link rel="prefetch" href="/blog/assets/js/8.00a36f19.js">
    <link rel="stylesheet" href="/blog/assets/css/0.styles.247a7745.css">
  </head>
  <body>
    <div id="app" data-server-rendered="true"><div id="global-layout" data-v-7f2e4136><header class="header-container" style="background-size:cover;background-repeat:no-repeat;background-position:center;background-attachment:scroll;background-image:url(/blog/post-bg.jpeg);" data-v-93921ff8 data-v-7f2e4136><nav class="navbar" style="position:absolute;opacity:1;transition:all 0.5s ease-in-out;" data-v-93921ff8><a href="/blog/" class="navbar-link router-link-active">
    Top Blog
  </a> <ul class="navbar-links"><li><a href="/blog/" class="router-link-active">
        HOME
      </a></li><li><a href="/blog/about/">
        ABOUT
      </a></li><li><a href="/blog/tags/">
        TAGS
      </a></li></ul> <div id="nav-icon"><span></span><span></span><span></span></div></nav> <div class="header-title" data-v-93921ff8 data-v-93921ff8><h1 data-v-93921ff8>深入 JS 之作用域和闭包</h1> <p data-v-93921ff8></p></div></header> <div class="container" data-v-b3fda33c data-v-7f2e4136><main class="main" style="width:60%;" data-v-b3fda33c><div class="post" data-v-b3fda33c data-v-b3fda33c><article class="main-div"><div class="post-content content content__default"><p>本篇博客主要是对 Javcscript 作用域和闭包学习理解
</p> <h2 id="作用域"><a href="#作用域" class="header-anchor">#</a> 作用域</h2> <p>编译原理对作用域的定义：</p> <blockquote><p>作用域是指声明的变量在程序中的一个区域，在其中对变量的使用都指向这个声明。如果仅通过阅读程序就可以确定一个变量的作用域，那么语言使用的是词法作用域，否则，这个语言使用的是动态作用域。</p></blockquote> <p>作用域用一句话概括就是规定如何查找变量，检查是否对变量有访问权限。</p> <p>JavaScript 采用词法作用域，也叫静态作用域。</p> <h2 id="编译"><a href="#编译" class="header-anchor">#</a> 编译</h2> <p>MDN 对 JavaScript 的定义为：</p> <blockquote><p>JavaScript 是一种解释型或即时编译型的编程语言。</p></blockquote> <p>JavaScript 语言是解释型语言，解释型语言的特点是逐个语句地执行源程序，会出现性能低下问题。而谷歌团队为解决 web 前端工作者的 JavaScript 性能低下问题，首次在脚本引擎（V8）中使用的即时编译（JIT），加上使用隐藏类和内联缓存等方法，使 JavaScript 语言拥有堪比主流编程语言（Java）的执行速度。</p> <p>思考下面 🌰 ：</p> <div class="language- extra-class"><pre class="language-text"><code>var greeting = &quot;Hello&quot;;

console.log(greeting);

var greeting = .&quot;Hello&quot;;

// SyntaxError: Unexpected token '.'
</code></pre></div><p>首先，根据解释型语言的定义，代码会在执行时被编译且逐行执行。上面代码执行结果应是先输出变量，接着赋值报错。但是实际结果是直接报错。是由于执行程序的前一刻会进行编译。</p> <p>JavaScript 脚本引擎的工作方式来进一步理解上面的 🌰：</p> <p>脚本引擎要开始解释执行，得通过词法分析和语法分析得到语法分析树后才可以。当要执行 script 代码段，它的执行顺序如下：</p> <ol><li>读入代码。</li> <li>做词法分析和语法分析，有错则报语法错误。</li> <li>对 var 变量和 function 定义做“预解析”。</li> <li>执行代码，有错则报错。</li></ol> <p>最后，我们可以清楚的明白，代码在程序执行的前一刻在编译做语法分析的时候，遇到语法错误所以直接报错，区别与传统的解释型语言，JavaScript 是解析器和编译器混合使用。程序最后其实并没有执行。</p> <p>上面过程，可以将 JavaScript 引擎工作方式归结两个阶段：解析和执行。</p> <ul><li>解析：通过语法分析和预解析构造合法的语法分析树。</li> <li>执行：执行具体的某个 function， JavaScript 引擎在执行每个函数实例时， 都会创建一个执行环境 (Execution Context) 和活动对象 (Active Object) 。</li></ul> <h2 id="词法作用域"><a href="#词法作用域" class="header-anchor">#</a> 词法作用域</h2> <p>要真正理解首先要先了解几个比较重要的概念。</p> <p>词法作用域定义：</p> <blockquote><p>词法作用域指变量的作用域不是执行时决定而是在定义时决定，也就是说通过静态分析就能确定，词法作用域取决于源码，因此词法作用域也叫做静态作用域。</p></blockquote> <p>ES5中使用词法环境管理词法作用域。ES6中作用域的概念变成了词法环境概念。</p> <p>编译原理对环境和状态的定义：</p> <blockquote><p>环境指的是名字到变量的映射（内存位置）。状态是内存位置到它们值的映射。</p></blockquote> <p>词法环境的定义：</p> <blockquote><p>词法环境 是一个用于定义特定变量和函数标识符在 ECMAScript 代码的词法嵌套结构上关联关系的规范类型。简单来说是 JavaScript 引擎内部用来跟踪标识符和变量之间的映射关系。</p></blockquote> <p>词法环境由一个环境记录项和可能为空的外部词法环境引用构成。</p> <p>变量环境组件和词法环境，又统称为词法环境。</p> <p>变量环境的定义：</p> <blockquote><p>变量标识符和函数标识符对内存位置的映射，简单来说声明的变量函数它们知道自己的数据存储在哪里。（等同于词法环境）</p></blockquote> <p>执行环境（也称执行上下文）的定义：</p> <blockquote><p>执行环境就是当前 JavaScript 代码被解析和执行时所在环境的抽象概念， JavaScript 中运行任何的代码都是在执行环境中运行。</p></blockquote> <p>每调用一个函数就会生成一个函数执行环境，script 标签里的代码可以看成是一个立即执行的“匿名函数”，这个被称为全局执行环境。</p> <p>ES5 执行环境组成部分：</p> <div class="language- extra-class"><pre class="language-text"><code>ExecutionContext = {
  ThisBinding: &lt;this value&gt;,
  VariableEnvironment: { ... }, // 变量环境
  LexicalEnvironment: { ... }   // 词法环境
}
</code></pre></div><p>进一步理解，来看下面 🌰：</p> <div class="language- extra-class"><pre class="language-text"><code>// __outer__：外部词法环境引用，x、y：环境记录项。
// 全局环境变量 VariableEnvironment (global) = { __outer__: null }
// 词法环境 LexicalEnvironment = VariableEnvironment (global)

// 立即执行函数foo
(function foo() {
  //1、 这里命名为A（随便取用于区分下面） 的变量环境
  //（可以理解为执行之前分配内存空间，初始化为undefined）
  // VariableEnvironment (A) = { x: undefined, __outer__: global } 
  
  //2、 词法环境等同于变量环境的原因，变量环境是词法环境的引用
  // LexicalEnvironment = VariableEnvironment (A) 

  var x;
  // 立即执行函数bar
  (function bar(){
    // 初始化词法环境
    // VariableEnvironment (B) = { y: undefined, __outer__: A }
    // LexicalEnvironment = VariableEnvironment (B)

    var y;

    x = 2;
    // 初始化词法环境完成之后，立即执行 x=2 语句，通过__outer__找到外部的x
    // VariableEnvironment (A) = { x: 2, __outer__: global }
    // bar LexicalEnvironment 仍然是 VariableEnvironment (B)

  })();

})();
</code></pre></div><p>创建执行环境时，变量环境和词法环境最初是同一个值（变量环境指向词法环境），在执行环境执行相关代码过程中，变量环境组件永远不变，而词法环境组件有可能改变（with、eval可以改变词法环境），在函数即将执行前，会对var和function声明进行预解析，然后执行代码。</p> <p>再看一个 🌰：</p> <div class="language- extra-class"><pre class="language-text"><code>var x = 10; // 语句会拆成两个阶段，预解析var x；执行代码 x = 10；

function foo() {
  var y = 20;
 console.log(x + y); // 30
}

// 词法环境由一个环境记录项（environmentRecord）和可能为空的
// 外部词法环境 （outer environment）引用构成。

// 执行环境执行之前的结果（创建函数阶段）
VariableEnvironment (global) = {
  environmentRecord: {
    // 分配内存默认为undefined；
    x: undefined
  },
  outer: null // 没有外部环境
};

//  &quot;foo&quot; 函数变量环境
VariableEnvironment (foo) = {
  environmentRecord: {
    y: undefined
  },
  outer: globalEnvironment // 指向全局环境
};


// 全局作用域下的环境执行之后的结果
VariableEnvironment (global) = {
  environmentRecord: {
    // 绑定的值
    x: 10
  },
  outer: null // 没有外部环境
};

//  &quot;foo&quot; 函数环境
VariableEnvironment (foo) = {
  environmentRecord: {
    y: 20
  },
  outer: globalEnvironment // 指向全局环境
};
</code></pre></div><p>执行结果流程图：</p> <p><img src="https://user-gold-cdn.xitu.io/2020/4/16/1717ea3374f17dff?w=452&amp;h=333&amp;f=png&amp;s=40934" alt=""></p> <p>通过上面个栗子，无论何时调用函数都会创建一个新的执行环境，被推入执行上下文栈，此外还会创建与之关联的词法环境和变量环境。新建的词法环境通过外部环境引用进行关联。在执行代码之前，词法环境会访问并注册在当前词法作用域中所声明的变量，词法环境通过变量环境跟踪变量的作用域。特别注意是对 var 声明的变量和 function 声明函数！！！</p> <div class="language- extra-class"><pre class="language-text"><code>console.log(a);
// Cannot access 'a' before initialization

let a = 1

console.log(b)
// undefined

var b = function() {
    console.log(&quot;我调用了个函数&quot;)
}
</code></pre></div><h2 id="闭包"><a href="#闭包" class="header-anchor">#</a> 闭包</h2> <p>MDN 中对闭包的定义：</p> <blockquote><p>函数和对其周围状态（词法环境）的引用捆绑在一起构成闭包。</p></blockquote> <p>面试官问什么是闭包？</p> <p>我想大部分小伙伴都是闭包就是能够读取其他函数内部变量的函数。</p> <p>说实话没深入研究之前，我也是背背表面概念（毕竟网上一大堆都是这样说的）。通过对上面词法作用域的理解以后，我们突然会觉得上面的 MDN 定义太准确了。仔细分析就是外函数里面嵌套一个内函数并返回内函数，但调用内函数的时候，会创建词法环境，外部环境引用（上文的outer）指向外函数词法环境，直到全局词法环境执行指向 null。即使外函数词法环境已经消失，但还是可以通过内函数的词法环境外部环境指向访问到，这就是闭包。调用 N 次内函数，就会创建 N 个词法环境和 N 个闭包。</p> <h2 id="写在最后"><a href="#写在最后" class="header-anchor">#</a> 写在最后</h2> <p>用心写篇文章不易，如果内容中有不妥的地方欢迎指正，当然博客只是学习参考的依据，真正要学到掌握还是需要自己下功夫和研究的。毕竟别人的理解并不一定是自己的理解。如果对你有所帮助欢迎点赞收藏，码字不易，大家加油！！！</p></div></article> <div class="main-div vssue"><!----></div></div></main> <aside class="aside" data-v-b3fda33c data-v-b3fda33c><div class="info-card main-div" data-v-1311ce9e data-v-b3fda33c><div class="info-card-header" style="background-size:cover;background-repeat:no-repeat;background-position:center;background-attachment:scroll;background-image:url(/blog/avatar-bg.jpeg);" data-v-1311ce9e><img src="/blog/avatar-top.jpeg" alt="melodydl" class="info-avatar" data-v-1311ce9e></div> <div class="info-card-body" data-v-1311ce9e><section class="info-name" data-v-1311ce9e>
      melodydl
    </section> <section class="info-desc" data-v-1311ce9e>In me the tiger sniffs the rose<br/>心有猛虎，细嗅蔷薇</section> <section class="info-contact" data-v-1311ce9e><section data-v-1311ce9e><span data-v-1311ce9e data-v-1311ce9e><svg class="icon" style="font-size:1em;" data-v-1311ce9e data-v-1311ce9e><use xlink:href="#icon-location" data-v-1311ce9e data-v-1311ce9e></use></svg><span class="info-text" data-v-1311ce9e data-v-1311ce9e>
          Shanghai, China
        </span></span></section> <!----> <section data-v-1311ce9e><span data-v-1311ce9e data-v-1311ce9e><svg class="icon" style="font-size:1em;" data-v-1311ce9e data-v-1311ce9e><use xlink:href="#icon-email" data-v-1311ce9e data-v-1311ce9e></use></svg><span class="info-text" data-v-1311ce9e data-v-1311ce9e>
          facecode@foxmail.com
        </span></span></section></section></div> <div class="info-card-footer" data-v-1311ce9e><p class="footer-sns-link" data-v-1311ce9e><a target="_blank" href="https://blog.csdn.net/weixin_44002432" class="sns-link" data-v-1311ce9e><span class="sns-icon" data-v-1311ce9e data-v-1311ce9e><svg class="icon" style="font-size:35px;" data-v-1311ce9e data-v-1311ce9e><use xlink:href="#icon-csdn" data-v-1311ce9e data-v-1311ce9e></use></svg></span></a><a target="_blank" href="https://weibo.com/u/5656925978" class="sns-link" data-v-1311ce9e><span class="sns-icon" data-v-1311ce9e data-v-1311ce9e><svg class="icon" style="font-size:35px;" data-v-1311ce9e data-v-1311ce9e><use xlink:href="#icon-weibo" data-v-1311ce9e data-v-1311ce9e></use></svg></span></a><a target="_blank" href="https://juejin.im/user/5ce784a0f265da1b8333673c" class="sns-link" data-v-1311ce9e><span class="sns-icon" data-v-1311ce9e data-v-1311ce9e><svg class="icon" style="font-size:35px;" data-v-1311ce9e data-v-1311ce9e><use xlink:href="#icon-juejin" data-v-1311ce9e data-v-1311ce9e></use></svg></span></a><a target="_blank" href="https://www.zhihu.com/people/sheng-tang-de-xing-kong" class="sns-link" data-v-1311ce9e><span class="sns-icon" data-v-1311ce9e data-v-1311ce9e><svg class="icon" style="font-size:35px;" data-v-1311ce9e data-v-1311ce9e><use xlink:href="#icon-zhihu" data-v-1311ce9e data-v-1311ce9e></use></svg></span></a><a target="_blank" href="https://github.com/youdeliang" class="sns-link" data-v-1311ce9e><span class="sns-icon" data-v-1311ce9e data-v-1311ce9e><svg class="icon" style="font-size:35px;" data-v-1311ce9e data-v-1311ce9e><use xlink:href="#icon-github" data-v-1311ce9e data-v-1311ce9e></use></svg></span></a></p></div></div> <div class="post-toc main-div aside-toc" style="position:relative;top:0;width:0px;" data-v-b3fda33c><h4>- CATALOG</h4> <div class="post-nav-toc"><ul><li><a href="/blog/posts/2020/05/01/%E6%B7%B1%E5%85%A5js%E4%B9%8B%E6%88%91%E6%98%AF%E5%A6%82%E4%BD%95%E6%90%9E%E6%87%82%E4%BD%9C%E7%94%A8%E5%9F%9F%E5%92%8C%E9%97%AD%E5%8C%85%E7%9A%84.html#作用域">作用域</a></li><li><a href="/blog/posts/2020/05/01/%E6%B7%B1%E5%85%A5js%E4%B9%8B%E6%88%91%E6%98%AF%E5%A6%82%E4%BD%95%E6%90%9E%E6%87%82%E4%BD%9C%E7%94%A8%E5%9F%9F%E5%92%8C%E9%97%AD%E5%8C%85%E7%9A%84.html#编译">编译</a></li><li><a href="/blog/posts/2020/05/01/%E6%B7%B1%E5%85%A5js%E4%B9%8B%E6%88%91%E6%98%AF%E5%A6%82%E4%BD%95%E6%90%9E%E6%87%82%E4%BD%9C%E7%94%A8%E5%9F%9F%E5%92%8C%E9%97%AD%E5%8C%85%E7%9A%84.html#词法作用域">词法作用域</a></li><li><a href="/blog/posts/2020/05/01/%E6%B7%B1%E5%85%A5js%E4%B9%8B%E6%88%91%E6%98%AF%E5%A6%82%E4%BD%95%E6%90%9E%E6%87%82%E4%BD%9C%E7%94%A8%E5%9F%9F%E5%92%8C%E9%97%AD%E5%8C%85%E7%9A%84.html#闭包">闭包</a></li><li><a href="/blog/posts/2020/05/01/%E6%B7%B1%E5%85%A5js%E4%B9%8B%E6%88%91%E6%98%AF%E5%A6%82%E4%BD%95%E6%90%9E%E6%87%82%E4%BD%9C%E7%94%A8%E5%9F%9F%E5%92%8C%E9%97%AD%E5%8C%85%E7%9A%84.html#写在最后">写在最后</a></li></ul></div></div></aside></div> <footer class="footer" data-v-7f2e4136><p class="footer-sns-link"><a target="_blank" href="https://blog.csdn.net/weixin_44002432" class="sns-link"><span class="sns-icon"><svg class="icon" style="font-size:40px;"><use xlink:href="#icon-csdn"></use></svg></span></a><a target="_blank" href="https://weibo.com/u/5656925978" class="sns-link"><span class="sns-icon"><svg class="icon" style="font-size:40px;"><use xlink:href="#icon-weibo"></use></svg></span></a><a target="_blank" href="https://juejin.im/user/5ce784a0f265da1b8333673c" class="sns-link"><span class="sns-icon"><svg class="icon" style="font-size:40px;"><use xlink:href="#icon-juejin"></use></svg></span></a><a target="_blank" href="https://www.zhihu.com/people/sheng-tang-de-xing-kong" class="sns-link"><span class="sns-icon"><svg class="icon" style="font-size:40px;"><use xlink:href="#icon-zhihu"></use></svg></span></a><a target="_blank" href="https://github.com/youdeliang" class="sns-link"><span class="sns-icon"><svg class="icon" style="font-size:40px;"><use xlink:href="#icon-github"></use></svg></span></a></p> <div class="copyright"><span id="custom">Copyright &copy; Top Blog 2020 <br /> 
        Theme By <a href="https://www.vuepress.cn/" target="_blank">VuePress</a>
        | <a href="https://www.github.com/youdeliang/" target="_blank">youdeliang</a></span> <iframe src="https://ghbtns.com/github-btn.html?user=youdeliang&amp;repo=vuepress-theme-melodydl&amp;type=star&amp;count=true" frameborder="0" scrolling="0" width="80px" height="20px" class="footer-btn" style="margin-left: 2px; margin-bottom:-5px;"></iframe></div></footer></div><div class="global-ui"><!----><!----></div></div>
    <script src="/blog/assets/js/app.a26f0e63.js" defer></script><script src="/blog/assets/js/6.dddf2d67.js" defer></script><script src="/blog/assets/js/9.876e8c50.js" defer></script>
  </body>
</html>
